# gozerbot/redispatcher.py
#
#

""" implement RE (regular expression) dispatcher. """

__copyright__ = 'this file is in the public domain'

## IMPORT SECTION

# gozerbot imports
from config import config
from utils.log import rlog
from utils.trace import calledfrom
from utils.exception import handle_exception
from utils.locking import lockdec
from runner import cmndrunners
import threads.thr as thr

# basic imports
import sys, re, copy, types, thread

## END IMPORT

## LOCK SECTION

# locks
relock = thread.allocate_lock()
locked = lockdec(relock)

## END LOCK

class RECallback(object):

    """
         a regular expression callback.

         :param index: index into the callback list
         :type index: integer
         :param regex: the regex to match
         :type regex: string
         :param func: the callback function
         :type func: function
         :param perm: permissions of the callback
         :type perm: list .. list of permissions
         :param speed: speed at which the callback should be executed
         :type speed: integer
         :param threaded: whether the callback should executed in its own thread
         :type threaded: boolean
         :param allowqueue: whether this command is allowed in pipelines
         :type allowqueue: boolean
         :param options: options allowed for this command
         :type options: dict

    """

    def __init__(self, index, regex, func, perm, plugname, speed=5, \
threaded=True, allowqueue=True, options={}):
        self.name = thr.getname(func) # name of the callback
        self.index = index # index into the list
        self.regex = regex # the RE to match
        self.compiled = re.compile(regex) # compiled RE
        self.func = func # the function to call if RE matches
        # make sure perms is a list
        if type(perm) == types.ListType:
            self.perms = list(perm)
        else:
            self.perms = [perm, ]
        # plug name
        self.plugname = plugname # plugname where RE callbacks is registered
        self.speed = copy.deepcopy(speed) # speed at which the function runs
        self.threaded = copy.deepcopy(threaded) # set when run threaade
        self.allowqueue = copy.deepcopy(allowqueue) # set when pipeline is allowed
        self.options = dict(options) # options set on the callback
        self.activate = True

class REDispatcher(object):

    """
        this is were the regexs callbacks live.

    """

    def __init__(self):
        self.relist = []
        
    def size(self):

        """
            nr of callbacks.

        """

        return len(self.relist)

    def activate(self, plugname):
        for i in self.relist:
            if i.plugname == plugname:
                i.activate = True

    def disable(self, plugname):
        for i in self.relist:
            if i.plugname == plugname:
                i.activate = False

    def whatperms(self):

        """
            return possible permissions.

            :rtype: list .. list of possible permissions

            .. literalinclude:: ../../gozerbot/redispatcher.py
                :pyobject: REDispatcher.whatperms

        """

        result = []

        for i in self.relist:
            for j in i.perms:
                if j not in result:
                    result.append(j)

        return result

    def list(self, perm):

        """
            list RECallbacks with permission perm.

            :param perm: permission to check for
            :type perm: string
            :rtype: list .. list of RECallbacks 

            .. literalinclude:: ../../gozerbot/redispatcher.py
                :pyobject: REDispatcher.list
        """

        result = []
        perm = perm.upper()

        for recom in self.relist:
            if perm in recom.perms:
                result.append(recom)

        return result

    def getfuncnames(self, plug):

        """
            return function names in plugin.

            :param plug: name of the plugin to get callbacks of
            :type plug: string
            :rtype: list .. list of function names

            .. literalinclude:: ../../gozerbot/redispatcher.py
                :pyobject: REDispatcher.getfuncnames

        """

        result = []
        for i in self.relist:
            if i.plugname == plug:
                result.append(i.func.func_name)
        return result
        
    def permoverload(self, funcname, perms):

        """
            overload permission of function with funcname.

            :param funcname: name of the function to overload
            :type funcname: string
            :param perms: permission to overload
            :type perms: list
            :rtype: boolean .. whether the overload succeeded

            .. literalinclude:: ../../gozerbot/redispatcher.py
                :pyobject: REDispatcher.permoverload

        """

        perms = [perm.upper() for perm in perms]
        got = 0

        for nr in range(len(self.relist)):

            try:
                if self.relist[nr].func.func_name == funcname:
                    self.relist[nr].perms = list(perms)
                    rlog(0, 'redispatcher', '%s function overloaded with %s' \
% (funcname, perms))
                    got = 1

            except AttributeError:
                rlog(10, 'redispatcher', 'permoverload: no %s function' % \
funcname)
        if got:
            return True

        return False

    def add(self, index, regex, func, perm, speed=5, threaded=True, allowqueue=True, options={}):

        """
            add a regular expression command.

           :param index: index into the callback list
           :type index: integer
           :param regex: the regex to match
           :type regex: string
           :param func: the callback function
           :type func: function
           :param perm: permissions of the callback
           :type perm: list .. list of permissions
           :param speed: speed at which the callback should be executed
           :type speed: integer
           :param threaded: whether the callback should executed in its own thread
           :type threaded: boolean
           :param allowqueue: whether this command is allowed in pipelines
           :type allowqueue: boolean
           :param options: options allowed for this command
           :type options: dict

           .. literalinclude:: ../../gozerbot/redispatcher.py
               :pyobject: REDispatcher.add

        """

        try:
            # get plugin name from where callback is added
            plugname = calledfrom(sys._getframe())

            if config['loadlist'] and plugname not in config['loadlist']:
                return
            # add Recallback

            self.relist.append(RECallback(index, regex, func, perm, plugname, \
speed, threaded, allowqueue, options))
            # sort of index number
            self.relist.sort(lambda a, b: cmp(a.index, b.index))
            rlog(0, 'redispatcher', 'added %s (%s) ' % (regex, plugname))

        finally:
            pass

    def unload(self, plugname):

        """
            unload regex commands.

            :param plugname: name of the plugins to unload callbacks from
            :type plugname: string
            :rtype: boolean .. whether the unloading succeeded

            .. literalinclude:: ../../gozerbot/redispatcher.py
                :pyobject: REDispatcher.unload

        """

        got = False
        try:

            for i in range(len(self.relist)-1, -1 , -1):
                if self.relist[i].plugname == plugname:
                    rlog(1, 'redispatcher', 'unloading %s (%s)' % \
(self.relist[i].regex, plugname))
                    del self.relist[i]
                    got = True
        finally:    
            pass
        return got

    def getcallback(self, txt):

        """
            get re callback if txt matches.

            :param txt: txt to match against the regular expressions
            :type txt: string

            .. literalinclude:: ../../gozerbot/redispatcher.py
                :pyobject: REDispatcher.getcallback
        """

        for i in self.relist:

            try:
                result = re.search(i.compiled, txt)

                if result:
                    return i

            except:
                pass

    def dispatch(self, callback, txt, wait=False):

        """
            try to dispatch callback on txt.
 
            :param callback: the callback to fire
            :type callback: RECallback
            :param txt: txt to match the regular expression
            :type txt: string
            :param wait: whether to wait for the result
            :type wait: boolean

            .. literalinclude:: ../../gozerbot/redispatcher.py
                :pyobject: REDispatcher.dispatch

        """

        try:
            result = re.search(callback.compiled, txt)

            if result:

                if callback.threaded:
                    thread = thr.start_new_thread(callback.func, (txt, result.groups()))

                    if wait:
                        thread.join()

                else:
                    cmndrunners.put(callback.plugname, callback.func, txt, \
result.groups())

                    return 1

        except Exception, ex:
            handle_exception()

class BotREDispatcher(REDispatcher):

    """
        dispatcher on Event.

    """

    def dispatch(self, callback, bot, ievent, wait=False):

        """
            dispatch callback on ircevent.

            :param callback: the callback to fire
            :type callback: RECallback
            :param bot: the bot on which the callback was triggered
            :type bot: gozerbot.botbase.BotBase
            :param ievent: the event that triggered the callback
            :type ievent: gozerbot.eventbase.EventBase
            :rtype: boolean .. whether the dispatch was succesful

            .. literalinclude:: ../../gozerbot/redispatcher.py
                :pyobject: BotREDispatcher.dispatch

        """

        if not self.activate:
            return False

        try:
            result = re.search(callback.compiled, ievent.txt.strip())

            if result:
                ievent.groups = list(result.groups())                

                if callback.threaded or ievent.threaded:
                    thread = thr.start_bot_command(callback.func, (bot, ievent))

                    if thread and wait:
                        thread.join()

                else:
                    cmndrunners.put(callback.plugname, callback.func, bot, \
ievent)
                return True

        except Exception, ex:
            handle_exception(ievent)

        return False

## INIT SECTION

# dispatcher before commands are checked
rebefore = BotREDispatcher()

# dispatcher after commands are checked
reafter = BotREDispatcher()

## END INIT
